home *** CD-ROM | disk | FTP | other *** search
Text File | 1992-04-25 | 24.7 KB | 1,083 lines |
- #import "Graphic.h"
- #import "GraphicView.h"
- #import "draw.h"
- #import <appkit/Application.h>
- #import <appkit/NXCursor.h>
- #import <appkit/Window.h>
- #import <appkit/defaults.h>
- #import <appkit/timer.h>
- #import <appkit/nextstd.h>
- #import <dpsclient/wraps.h>
- #import <math.h>
-
- @implementation Graphic : Object
-
- static int KNOB_WIDTH = 0.0;
- static int KNOB_HEIGHT = 0.0;
-
- #define MINSIZE 5.0 /* minimum size of a Graphic */
-
- id CrossCursor = nil; /* global since subclassers may need it */
-
- /* Optimization method. */
-
- static NXRect *blackRectList = NULL;
- static int blackRectSize = 0;
- static int blackRectCount = 0;
- static NXRect *dkgrayRectList = NULL;
- static int dkgrayRectSize = 0;
- static int dkgrayRectCount = 0;
-
- + fastRectFill:(const NXRect *)aRect isBlack:(BOOL)isBlack
- {
- if (!aRect) return self;
-
- if (isBlack) {
- if (!blackRectList) {
- blackRectSize = 16;
- NX_ZONEMALLOC([NXApp zone], blackRectList, NXRect, blackRectSize);
- } else {
- while (blackRectCount >= blackRectSize) blackRectSize <<= 1;
- NX_ZONEREALLOC([NXApp zone], blackRectList, NXRect, blackRectSize);
- }
- blackRectList[blackRectCount++] = *aRect;
- } else {
- if (!dkgrayRectList) {
- dkgrayRectSize = 16;
- NX_ZONEMALLOC([NXApp zone], dkgrayRectList, NXRect, dkgrayRectSize);
- } else {
- while (dkgrayRectCount >= dkgrayRectSize) dkgrayRectSize <<= 1;
- NX_ZONEREALLOC([NXApp zone], dkgrayRectList, NXRect, dkgrayRectSize);
- }
- dkgrayRectList[dkgrayRectCount++] = *aRect;
- }
-
- return self;
- }
-
- + showFastRectFills
- {
- PSsetgray(NX_BLACK);
- NXRectFillList(blackRectList, blackRectCount);
- PSsetgray(NX_DKGRAY);
- NXRectFillList(dkgrayRectList, dkgrayRectCount);
- blackRectCount = 0;
- dkgrayRectCount = 0;
- return self;
- }
-
- /* Factory methods. */
-
- + initialize
- /*
- * This bumps the class version so that we can compatibly read
- * old Graphic objects out of an archive.
- */
- {
- [self setVersion:1];
- return self;
- }
-
- + (BOOL)isEditable
- /*
- * Any Graphic which can be edited should return YES from this
- * and its instances should do something in the response to the
- * edit:in: method.
- */
- {
- return NO;
- }
-
- + cursor
- /*
- * Any Graphic that doesn't have a special cursor gets the default cross.
- */
- {
- NXPoint spot;
-
- if (!CrossCursor) {
- CrossCursor = [NXCursor newFromImage:[NXImage newFromSection:"cross.tiff"]];
- spot.x = 7.0; spot.y = 7.0;
- [CrossCursor setHotSpot:&spot];
- }
-
- return CrossCursor;
- }
-
- static void initClassVars()
- {
- const char *value;
- NXCoord w = 2.0, h = 2.0;
-
- if (!KNOB_WIDTH) {
- value = NXGetDefaultValue([NXApp appName], "KnobWidth");
- if (value) w = floor(atof(value) / 2.0);
- value = NXGetDefaultValue([NXApp appName], "KnobHeight");
- if (value) h = floor(atof(value) / 2.0);
- w = MAX(w, 1.0); h = MAX(h, 1.0);
- KNOB_WIDTH = w * 2.0 + 1.0; /* size must be odd */
- KNOB_HEIGHT = h * 2.0 + 1.0;
- }
- }
-
- - init
- {
- [super init];
- gFlags.active = YES;
- gFlags.selected = YES;
- initClassVars();
- return self;
- }
-
- - awake
- {
- initClassVars();
- return [super awake];
- }
-
- /* Private C functions and macros used to implement methods in this class. */
-
- #define stopTimer(timer) if (timer) { \
- NXEndTimer(timer); \
- timer = NULL; \
- }
-
- #define startTimer(timer) if (!timer) timer = NXBeginTimer(NULL, 0.1, 0.1);
-
- static void drawKnobs(const NXRect *rect, int cornerMask, BOOL black)
- /*
- * Draws either the knobs or their shadows (not both).
- */
- {
- NXRect knob;
- NXCoord dx, dy;
- BOOL oddx, oddy;
-
- knob = *rect;
- dx = knob.size.width / 2.0;
- dy = knob.size.height / 2.0;
- oddx = (floor(dx) != dx);
- oddy = (floor(dy) != dy);
- knob.size.width = KNOB_WIDTH;
- knob.size.height = KNOB_HEIGHT;
- knob.origin.x -= ((KNOB_WIDTH - 1.0) / 2.0);
- knob.origin.y -= ((KNOB_HEIGHT - 1.0) / 2.0);
-
- if (cornerMask & LOWER_LEFT_MASK) [Graphic fastRectFill:&knob isBlack:black];
- knob.origin.y += dy;
- if (oddy) knob.origin.y -= 0.5;
- if (cornerMask & LEFT_SIDE_MASK) [Graphic fastRectFill:&knob isBlack:black];
- knob.origin.y += dy;
- if (oddy) knob.origin.y += 0.5;
- if (cornerMask & UPPER_LEFT_MASK) [Graphic fastRectFill:&knob isBlack:black];
- knob.origin.x += dx;
- if (oddx) knob.origin.x -= 0.5;
- if (cornerMask & TOP_SIDE_MASK) [Graphic fastRectFill:&knob isBlack:black];
- knob.origin.x += dx;
- if (oddx) knob.origin.x += 0.5;
- if (cornerMask & UPPER_RIGHT_MASK) [Graphic fastRectFill:&knob isBlack:black];
- knob.origin.y -= dy;
- if (oddy) knob.origin.y -= 0.5;
- if (cornerMask & RIGHT_SIDE_MASK) [Graphic fastRectFill:&knob isBlack:black];
- knob.origin.y -= dy;
- if (oddy) knob.origin.y += 0.5;
- if (cornerMask & LOWER_RIGHT_MASK) [Graphic fastRectFill:&knob isBlack:black];
- knob.origin.x -= dx;
- if (oddx) knob.origin.x += 0.5;
- if (cornerMask & BOTTOM_SIDE_MASK) [Graphic fastRectFill:&knob isBlack:black];
- }
-
- /* Private methods sometimes overridden by subclassers */
-
- - setGraphicsState
- {
- PSSetParameters(gFlags.linecap, gFlags.linejoin, linewidth);
- return self;
- }
-
- - setLineColor
- {
- if (lineColor) {
- NXSetColor(*lineColor);
- } else {
- PSsetgray(NX_BLACK);
- }
- return self;
- }
-
- - setFillColor
- {
- if (fillColor) NXSetColor(*fillColor);
- return self;
- }
-
- - (int)cornerMask
- /*
- * Returns a mask of the corners which should have a knobby in them.
- */
- {
- return ALL_CORNERS;
- }
-
- /* Public routines mostly called by GraphicView's. */
-
- - (BOOL)isSelected
- {
- return gFlags.selected;
- }
-
- - (BOOL)isActive
- {
- return gFlags.active;
- }
-
- - (BOOL)isLocked
- {
- return gFlags.locked;
- }
-
- - select
- {
- gFlags.selected = YES;
- return self;
- }
-
- - deselect
- {
- gFlags.selected = NO;
- return self;
- }
-
- - activate
- /*
- * Activation is used to *temporarily* take a Graphic out of the GraphicView.
- */
- {
- gFlags.active = YES;
- return self;
- }
-
- - deactivate
- {
- gFlags.active = NO;
- return self;
- }
-
- - lock
- /*
- * A locked graphic cannot be selected, resized or moved.
- */
- {
- gFlags.locked = YES;
- return self;
- }
-
- - unlock
- {
- gFlags.locked = NO;
- return self;
- }
-
- - setCacheable:(BOOL)flag
- {
- return self;
- }
-
- - (BOOL)isCacheable
- {
- return YES;
- }
-
- - getBounds:(NXRect *)theRect
- {
- *theRect = bounds;
- return self;
- }
-
- - setBounds:(const NXRect *)aRect
- {
- bounds = *aRect;
- return self;
- }
-
- - (NXRect *)getExtendedBounds:(NXRect *)theRect
- /*
- * Returns, by reference, the rectangle which encloses the Graphic
- * AND ITS KNOBBIES and its increased line width (if appropriate).
- */
- {
- if (bounds.size.width < 0.0) {
- theRect->origin.x = bounds.origin.x + bounds.size.width;
- theRect->size.width = - bounds.size.width;
- } else {
- theRect->origin.x = bounds.origin.x;
- theRect->size.width = bounds.size.width;
- }
- if (bounds.size.height < 0.0) {
- theRect->origin.y = bounds.origin.y + bounds.size.height;
- theRect->size.height = - bounds.size.height;
- } else {
- theRect->origin.y = bounds.origin.y;
- theRect->size.height = bounds.size.height;
- }
-
- theRect->size.width = MAX(1.0, theRect->size.width);
- theRect->size.height = MAX(1.0, theRect->size.height);
-
- NXInsetRect(theRect, - ((KNOB_WIDTH - 1.0) + linewidth + 1.0),
- - ((KNOB_HEIGHT - 1.0) + linewidth + 1.0));
-
- if (gFlags.arrow) {
- if (linewidth) {
- NXInsetRect(theRect, - linewidth * 2.5, - linewidth * 2.5);
- } else {
- NXInsetRect(theRect, - 13.0, - 13.0);
- }
- NXIntegralRect(theRect);
- }
-
- return theRect;
- }
-
- - (int)knobHit:(const NXPoint *)p
- /*
- * Returns 0 if point is in bounds, and Graphic isOpaque, and no knobHit.
- * Returns -1 if outside bounds or not opaque or not active.
- * Returns corner number if there is a hit on a corner.
- * We have to be careful when the bounds are off an odd size since the
- * knobs on the sides are one pixel larger.
- */
- {
- NXRect eb;
- NXRect knob;
- NXCoord dx, dy;
- BOOL oddx, oddy;
- int cornerMask = [self cornerMask];
-
- [self getExtendedBounds:&eb];
-
- if (!gFlags.active) {
- return -1;
- } else if (!gFlags.selected) {
- return (NXMouseInRect(p, &bounds, NO) && [self isOpaque]) ? 0 : -1;
- } else {
- if (!NXMouseInRect(p, &eb, NO)) return -1;
- }
-
- knob = bounds;
- dx = knob.size.width / 2.0;
- dy = knob.size.height / 2.0;
- oddx = (floor(dx) != dx);
- oddy = (floor(dy) != dy);
- knob.size.width = KNOB_WIDTH;
- knob.size.height = KNOB_HEIGHT;
- knob.origin.x -= ((KNOB_WIDTH - 1.0) / 2.0);
- knob.origin.y -= ((KNOB_HEIGHT - 1.0) / 2.0);
-
- if ((cornerMask & LOWER_LEFT_MASK) && NXMouseInRect(p, &knob, NO))
- return(LOWER_LEFT);
- knob.origin.y += dy;
- if (oddy) knob.origin.y -= 0.5;
- if ((cornerMask & LEFT_SIDE_MASK) && NXMouseInRect(p, &knob, NO))
- return(LEFT_SIDE);
- knob.origin.y += dy;
- if (oddy) knob.origin.y += 0.5;
- if ((cornerMask & UPPER_LEFT_MASK) && NXMouseInRect(p, &knob, NO))
- return(UPPER_LEFT);
- knob.origin.x += dx;
- if (oddx) knob.origin.x -= 0.5;
- if ((cornerMask & TOP_SIDE_MASK) && NXMouseInRect(p, &knob, NO))
- return(TOP_SIDE);
- knob.origin.x += dx;
- if (oddx) knob.origin.x += 0.5;
- if ((cornerMask & UPPER_RIGHT_MASK) && NXMouseInRect(p, &knob, NO))
- return(UPPER_RIGHT);
- knob.origin.y -= dy;
- if (oddy) knob.origin.y -= 0.5;
- if ((cornerMask & RIGHT_SIDE_MASK) && NXMouseInRect(p, &knob, NO))
- return(RIGHT_SIDE);
- knob.origin.y -= dy;
- if (oddy) knob.origin.y += 0.5;
- if ((cornerMask & LOWER_RIGHT_MASK) && NXMouseInRect(p, &knob, NO))
- return(LOWER_RIGHT);
- knob.origin.x -= dx;
- if (oddx) knob.origin.x += 0.5;
- if ((cornerMask & BOTTOM_SIDE_MASK) && NXMouseInRect(p, &knob, NO))
- return(BOTTOM_SIDE);
-
- return NXMouseInRect(p, &bounds, NO) ? ([self isOpaque] ? 0 : -1) : -1;
- }
-
- - draw:(const NXRect *)rect
- /*
- * Draws the graphic inside rect. If rect is NULL, then it draws the
- * entire Graphic. If the Graphic is not intersected by rect, then it
- * is not drawn at all. If the Graphic is selected, it is drawn with
- * its knobbies. This method is not intended to be overridden. It
- * calls the overrideable method "draw" which doesn't have to worry
- * about drawing the knobbies.
- */
- {
- NXRect r;
-
- [self getExtendedBounds:&r];
- if (gFlags.active && (!rect || NXIntersectsRect(rect, &r))) {
- if ([self isOpaque]) [Graphic showFastRectFills];
- [self setGraphicsState]; /* does a gsave */
- [self draw];
- PSgrestore(); /* so we need a grestore here */
- if (gFlags.selected && NXDrawingStatus == NX_DRAWING) {
- r = bounds;
- r.origin.x += 1.0;
- r.origin.y -= 1.0;
- drawKnobs(&r, [self cornerMask], YES); /* shadows */
- r = bounds;
- drawKnobs(&r, [self cornerMask], NO); /* knobs */
- }
- return self;
- }
-
- return nil;
- }
-
- - moveLeftEdgeTo:(NXCoord *)x
- {
- bounds.origin.x = *x;
- return self;
- }
-
- - moveRightEdgeTo:(NXCoord *)x
- {
- bounds.origin.x = *x - bounds.size.width;
- return self;
- }
-
- - moveTopEdgeTo:(NXCoord *)y
- {
- bounds.origin.y = *y - bounds.size.height;
- return self;
- }
-
- - moveBottomEdgeTo:(NXCoord *)y
- {
- bounds.origin.y = *y;
- return self;
- }
-
- - moveHorizontalCenterTo:(NXCoord *)x
- {
- bounds.origin.x = *x - floor(bounds.size.width / 2.0);
- return self;
- }
-
- - moveVerticalCenterTo:(NXCoord *)y
- {
- bounds.origin.y = *y - floor(bounds.size.height / 2.0);
- return self;
- }
-
- - moveBy:(const NXPoint *)offset
- {
- bounds.origin.x += floor(offset->x);
- bounds.origin.y += floor(offset->y);
- return self;
- }
-
- - moveTo:(const NXPoint *)p
- {
- bounds.origin.x = floor(p->x);
- bounds.origin.y = floor(p->y);
- return self;
- }
-
- - centerAt:(const NXPoint *)p
- {
- bounds.origin.x = floor(p->x - bounds.size.width / 2.0);
- bounds.origin.y = floor(p->y - bounds.size.height / 2.0);
- return self;
- }
-
- - sizeTo:(const NXSize *)size
- {
- bounds.size.width = floor(size->width);
- bounds.size.height = floor(size->height);
- return self;
- }
-
- - sizeToNaturalAspectRatio
- {
- return [self constrainCorner:UPPER_RIGHT
- toAspectRatio:[self naturalAspectRatio]];
- }
-
- - sizeToGrid:graphicView
- {
- NXPoint p;
-
- [graphicView grid:&bounds.origin];
- p.x = bounds.origin.x + bounds.size.width;
- p.y = bounds.origin.y + bounds.size.height;
- [graphicView grid:&p];
- bounds.size.width = p.x - bounds.origin.x;
- bounds.size.height = p.y - bounds.origin.y;
-
- return self;
- }
-
- - alignToGrid:graphicView
- {
- [graphicView grid:&bounds.origin];
- return self;
- }
-
- /* Compatibility methods. */
-
- - replaceWithImage
- {
- return self;
- }
-
- /* Public routines. */
-
- - setLineWidth:(const float *)value
- /*
- * This is called with value indirected so that it can be called via
- * a perform:with: method. Kind of screwy, but ...
- */
- {
- if (value) linewidth = *value;
- return self;
- }
-
- - (float)lineWidth
- {
- return linewidth;
- }
-
- - setLineColor:(NXColor *)color
- {
- if (color) {
- if (NXEqualColor(*color, NX_COLORBLACK)) {
- NX_FREE(lineColor);
- lineColor = NULL;
- } else {
- if (!lineColor) NX_ZONEMALLOC([self zone], lineColor, NXColor, 1);
- *lineColor = *color;
- }
- }
- return self;
- }
-
- - (NXColor)lineColor
- {
- return lineColor ? *lineColor : NX_COLORBLACK;
- }
-
- - setFillColor:(NXColor *)color
- {
- if (color) {
- if (NXEqualColor(*color, NX_COLORCLEAR)) {
- NX_FREE(fillColor);
- fillColor = NULL;
- } else {
- if (!fillColor) NX_ZONEMALLOC([self zone], fillColor, NXColor, 1);
- *fillColor = *color;
- }
- }
- return self;
- }
-
- - (NXColor)fillColor
- {
- return fillColor ? *fillColor : NX_COLORCLEAR;
- }
-
- - setGraphicTextColor:(NXColor *)color
- /*
- * By default Graphics don't have a "Text color".
- */
- {
- return self;
- }
-
- - (NXColor)textColor
- {
- return NX_COLORCLEAR;
- }
-
- - changeFont:sender
- {
- return self;
- }
-
- - font
- {
- return nil;
- }
-
- - (BOOL)wantsTextColor
- {
- return NO;
- }
-
- - (BOOL)wantsLineColor
- {
- return fillColor ? NO : YES;
- }
-
- - (BOOL)wantsFillColor
- {
- return fillColor ? YES : NO;
- }
-
- - setGray:(const float *)value
- /*
- * This is called with value indirected so that it can be called via
- * a perform:with: method. Kind of screwy, but ...
- * Now that we have converted to using NXColor's, we'll interpret this
- * method as a request to set the lineColor.
- */
- {
- NXColor color;
-
- if (value) {
- color = NXConvertGrayToColor(*value);
- [self setLineColor:&color];
- }
-
- return self;
- }
-
- - (float)gray
- {
- float retval;
-
- if (lineColor) {
- NXConvertColorToGray(*lineColor, &retval);
- } else {
- retval = NX_BLACK;
- }
-
- return retval;
- }
-
- - setFill:(int)mode
- /*
- * If mode = 0 -> don't fill at all, = 1 -> eofill, = 2 -> fill.
- */
- {
- switch (mode) {
- case 0: gFlags.eofill = gFlags.fill = NO; break;
- case 1: gFlags.eofill = YES; gFlags.fill = NO; break;
- case 2: gFlags.eofill = NO; gFlags.fill = YES; break;
- }
- if (!mode) {
- NX_FREE(fillColor);
- fillColor = NULL;
- }
- return self;
- }
-
- - (int)fill
- {
- if (!fillColor) {
- return 0;
- } else if (gFlags.eofill) {
- return 1;
- } else if (gFlags.fill) {
- return 2;
- } else {
- return 0;
- }
- }
-
- - (BOOL)isFilled
- {
- return fillColor && (gFlags.fill || gFlags.eofill);
- }
-
- - setLineCap:(int)capValue
- {
- if (capValue >= 0 && capValue <= 2) {
- gFlags.linecap = capValue;
- }
- return self;
- }
-
- - (int)lineCap
- {
- return gFlags.linecap;
- }
-
- - setLineArrow:(int)arrowValue
- {
- if (arrowValue >= 0 && arrowValue <= 3) {
- gFlags.arrow = arrowValue;
- }
- return self;
- }
-
- - (int)lineArrow
- {
- return gFlags.arrow;
- }
-
- - setLineJoin:(int)joinValue
- {
- if (joinValue >= 0 && joinValue <= 2) {
- gFlags.linejoin = joinValue;
- }
- return self;
- }
-
- - (int)lineJoin
- {
- return gFlags.linejoin;
- }
-
- /* Archiver-related methods. */
-
- - write:(NXTypedStream *)stream
- /*
- * Since a typical document has many Graphics, we want to try and make
- * the archived document small, so we don't write out the linewidth and
- * gray values if they are the most common 0 and NX_BLACK. To accomplish
- * this, we note that we haven't written them out by setting the
- * bits in gFlags.
- */
- {
- [super write:stream];
- gFlags.linewidthSet = (linewidth != 0.0);
- gFlags.lineColorSet = lineColor ? YES : NO;
- gFlags.fillColorSet = fillColor ? YES : NO;
- NXWriteTypes(stream, "ffffs", &bounds.origin.x, &bounds.origin.y,
- &bounds.size.width, &bounds.size.height, &gFlags);
- if (gFlags.linewidthSet) NXWriteTypes(stream, "f", &linewidth);
- if (gFlags.lineColorSet) NXWriteColor(stream, *lineColor);
- if (gFlags.fillColorSet) NXWriteColor(stream, *fillColor);
- return self;
- }
-
- - read:(NXTypedStream *)stream
- {
- float gray;
- int version;
-
- [super read:stream];
- version = NXTypedStreamClassVersion(stream, "Graphic");
- NXReadTypes(stream, "ffffs", &bounds.origin.x, &bounds.origin.y,
- &bounds.size.width, &bounds.size.height, &gFlags);
- if (gFlags.linewidthSet) NXReadTypes(stream, "f", &linewidth);
- if (version < 1) {
- if (gFlags.lineColorSet) NXReadTypes(stream, "f", &gray);
- if (gFlags.fillColorSet && (gFlags.eofill | gFlags.fill)) {
- NX_ZONEMALLOC([self zone], lineColor, NXColor, 1);
- *lineColor = NXConvertGrayToColor(gray);
- NX_ZONEMALLOC([self zone], fillColor, NXColor, 1);
- *fillColor = NXConvertGrayToColor(gray);
- } else if (gFlags.eofill | gFlags.fill) {
- NX_ZONEMALLOC([self zone], fillColor, NXColor, 1);
- *fillColor = NXConvertGrayToColor(gray);
- NX_ZONEMALLOC([self zone], lineColor, NXColor, 1);
- *lineColor = NX_COLORCLEAR;
- } else {
- NX_ZONEMALLOC([self zone], lineColor, NXColor, 1);
- *lineColor = NXConvertGrayToColor(gray);
- }
- } else {
- if (gFlags.lineColorSet) {
- NX_ZONEMALLOC([self zone], lineColor, NXColor, 1);
- *lineColor = NXReadColor(stream);
- }
- if (gFlags.fillColorSet) {
- NX_ZONEMALLOC([self zone], fillColor, NXColor, 1);
- *fillColor = NXReadColor(stream);
- }
- }
-
- return self;
- }
-
- /* Routines which may need subclassing for different Graphic types. */
-
- - constrainCorner:(int)corner toAspectRatio:(float)aspect
- /*
- * Modifies the bounds rectangle by moving the specified corner so that
- * the Graphic maintains the specified aspect ratio. This is used during
- * constrained resizing. Can be overridden if the aspect ratio is not
- * sufficient to constrain resizing.
- */
- {
- int newcorner;
- float actualAspect;
-
- if (!bounds.size.height || !bounds.size.width || !aspect) return self;
- actualAspect = bounds.size.width / bounds.size.height;
- if (actualAspect == aspect) return self;
-
- switch (corner) {
- case LEFT_SIDE:
- bounds.origin.x -= bounds.size.height * aspect-bounds.size.width;
- case RIGHT_SIDE:
- bounds.size.width = bounds.size.height * aspect;
- if (bounds.size.width) NXIntegralRect(&bounds);
- return self;
- case BOTTOM_SIDE:
- bounds.origin.y -= bounds.size.width / aspect-bounds.size.height;
- case TOP_SIDE:
- bounds.size.height = bounds.size.width / aspect;
- if (bounds.size.height) NXIntegralRect(&bounds);
- return self;
- case LOWER_LEFT:
- corner = 0;
- case 0:
- case UPPER_RIGHT:
- case UPPER_LEFT:
- case LOWER_RIGHT:
- if (actualAspect > aspect) {
- newcorner = ((corner|KNOB_DY_ONCE)&(~(KNOB_DY_TWICE)));
- } else {
- newcorner = ((corner|KNOB_DX_ONCE)&(~(KNOB_DX_TWICE)));
- }
- return [self constrainCorner:newcorner toAspectRatio:aspect];
- default:
- return self;
- }
- }
-
- #define RESIZE_MASK (NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK|NX_TIMERMASK)
-
- - resize:(NXEvent *)event by:(int)corner in:view
- /*
- * Resizes the graphic by the specified corner. If corner == CREATE,
- * then it is resized by the UPPER_RIGHT corner, but the initial size
- * is reset to 1 by 1.
- */
- {
- NXPoint p, last;
- float aspect = 0.0;
- id window = [view window];
- BOOL constrain, canScroll;
- DrawStatusType oldDrawStatus;
- NXTrackingTimer *timer = NULL;
- NXRect eb, oldeb, visibleRect;
-
- if (!gFlags.active || !gFlags.selected || !corner) return self;
-
- constrain = ((event->flags & NX_ALTERNATEMASK) &&
- ((bounds.size.width && bounds.size.height) || corner == CREATE));
- if (constrain) aspect = bounds.size.width / bounds.size.height;
- if (corner == CREATE) {
- bounds.size.width = bounds.size.height = 1.0;
- corner = UPPER_RIGHT;
- }
-
- gFlags.selected = NO;
-
- [self getExtendedBounds:&eb];
- [view lockFocus];
- gFlags.active = NO;
- [view cache:&eb];
- gFlags.active = YES;
- [self draw:NULL];
- [window flushWindow];
-
- oldDrawStatus = DrawStatus;
- DrawStatus = Resizing;
-
- [view getVisibleRect:&visibleRect];
- canScroll = !NXEqualRect(&visibleRect, &bounds);
- if (canScroll) startTimer(timer);
-
- while (event->type != NX_MOUSEUP) {
- p = event->location;
- event = [NXApp getNextEvent:RESIZE_MASK];
- if (event->type == NX_TIMER) event->location = p;
- p = event->location;
- [view convertPoint:&p fromView:nil];
- [view grid:&p];
- if (p.x != last.x || p.y != last.y) {
- corner = [self moveCorner:corner to:&p constrain:constrain];
- if (constrain) [self constrainCorner:corner toAspectRatio:aspect];
- oldeb = eb;
- [self getExtendedBounds:&eb];
- [window disableFlushWindow];
- [view drawSelf:&oldeb :1];
- if (canScroll) {
- [view scrollRectToVisible:&bounds];
- [view scrollPointToVisible:&p];
- }
- [self draw:NULL];
- [view tryToPerform:@selector(updateRulers:) with:(void *)&bounds];
- [window reenableFlushWindow];
- [window flushWindow];
- last = p;
- NXPing();
- }
- }
-
- if (canScroll) stopTimer(timer);
-
- gFlags.selected = YES;
- DrawStatus = oldDrawStatus;
-
- [view cache:&eb];
- [view tryToPerform:@selector(updateRulers:) with:nil];
- [window flushWindow];
- [view unlockFocus];
-
- return self;
- }
-
- - (BOOL)create:(NXEvent *)event in:view
- /*
- * This method rarely needs to be subclassed.
- * It sets up an initial bounds, and calls resize:by:in:.
- */
- {
- BOOL valid;
- NXCoord gridSpacing;
-
- bounds.origin = event->location;
- [view convertPoint:&bounds.origin fromView:nil];
- [view grid:&bounds.origin];
-
- gridSpacing = (NXCoord)[view gridSpacing];
- bounds.size.height = gridSpacing;
- bounds.size.width = gridSpacing * [self naturalAspectRatio];
-
- [self resize:event by:CREATE in:view];
-
- valid = [self isValid];
-
- if (valid) {
- gFlags.selected = YES;
- gFlags.active = YES;
- } else {
- gFlags.selected = NO;
- gFlags.active = NO;
- [view display];
- }
-
- return valid;
- }
-
- - (BOOL)hit:(const NXPoint *)p
- {
- return(!gFlags.locked && gFlags.active && NXMouseInRect(p, &bounds, NO));
- }
-
- - (BOOL)isOpaque
- {
- return gFlags.fill || gFlags.eofill;
- }
-
- - (BOOL)isValid
- /*
- * Called after a Graphic is created to see if it is valid (this usually
- * means "is it big enough?").
- */
- {
- return(bounds.size.width > MINSIZE && bounds.size.height > MINSIZE);
- }
-
- - (float)naturalAspectRatio
- /*
- * A natural aspect ratio of zero means it doesn't have a natural aspect ratio.
- */
- {
- return 0.0;
- }
-
- - (int)moveCorner:(int)corner to:(const NXPoint *)p constrain:(BOOL)flag
- /*
- * Moves the specified corner to the specified point.
- * Returns the position of the corner after it was moved.
- */
- {
- int newcorner = corner;
-
- if ((corner & KNOB_DX_ONCE) && (corner & KNOB_DX_TWICE)) {
- bounds.size.width += p->x - (bounds.origin.x + bounds.size.width);
- if (bounds.size.width <= 0.0) {
- newcorner &= ~ (KNOB_DX_ONCE | KNOB_DX_TWICE);
- bounds.origin.x += bounds.size.width;
- bounds.size.width = - bounds.size.width;
- }
- } else if (!(corner & KNOB_DX_ONCE)) {
- bounds.size.width += bounds.origin.x - p->x;
- bounds.origin.x = p->x;
- if (bounds.size.width <= 0.0) {
- newcorner |= KNOB_DX_ONCE | KNOB_DX_TWICE;
- bounds.origin.x += bounds.size.width;
- bounds.size.width = - bounds.size.width;
- }
- }
-
- if ((corner & KNOB_DY_ONCE) && (corner & KNOB_DY_TWICE)) {
- bounds.size.height += p->y - (bounds.origin.y + bounds.size.height);
- if (bounds.size.height <= 0.0) {
- newcorner &= ~ (KNOB_DY_ONCE | KNOB_DY_TWICE);
- bounds.origin.y += bounds.size.height;
- bounds.size.height = - bounds.size.height;
- }
- } else if (!(corner & KNOB_DY_ONCE)) {
- bounds.size.height += bounds.origin.y - p->y;
- bounds.origin.y = p->y;
- if (bounds.size.height <= 0.0) {
- newcorner |= KNOB_DY_ONCE | KNOB_DY_TWICE;
- bounds.origin.y += bounds.size.height;
- bounds.size.height = - bounds.size.height;
- }
- }
-
- if (newcorner != LOWER_LEFT) newcorner &= 0xf;
- if (!newcorner) newcorner = LOWER_LEFT;
-
- return newcorner;
- }
-
- - unitDraw
- /*
- * If a Graphic just wants to draw itself in the bounding box of
- * {{0.0,0.0},{1.0,1.0}}, it can simply override this method.
- * Everything else will work fine.
- */
- {
- return self;
- }
-
- - draw
- /*
- * Almost all Graphics need to override this method.
- * It does the Graphic-specific drawing.
- * By default, it scales the coordinate system and calls unitDraw.
- */
- {
- if (bounds.size.width >= 1.0 && bounds.size.height >= 1.0) {
- PStranslate(bounds.origin.x, bounds.origin.y);
- PSscale(bounds.size.width, bounds.size.height);
- [self unitDraw];
- }
- return self;
- }
-
- - (BOOL)edit:(NXEvent *)event in:view
- /*
- * Any Graphic which has editable text should override this method
- * to edit that text. TextGraphic is an example.
- */
- {
- return NO;
- }
-
-
- /* Methods added to support Hippo drawing */
-
- - forward:(SEL)aSelector :(marg_list)argFrame
- {
- return self;
- }
- @end
-
-